package org.testis.serialportmodule.core;

import org.testis.logging.AtofLogLevel;
import org.testis.logging.LogSenderService;
import org.testis.utils.OsInfo;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.TooManyListenersException;

/**
 *
 * @author nurhat
 */
public class SerialPortProvider implements SerialConnectionInterface,
        SerialPortEventListener {

    private CommPortIdentifier m_portId;
    private SerialPort m_serialPort;
    private InputStream m_inputStream;
    private OutputStream m_outputStream;
    private int m_comPortNo;
    private int m_baudRate;
    private int m_stopBits;
    private int m_parity;
    private int m_flowControl;
    private int m_dataBits;
    private String m_messageId;
    private StringBuilder m_outputStringBuilder;
    private StringBuilder m_commandOutputStringBuilder;
    private boolean m_sendingCommand = false;

    public SerialPortProvider(int comPortNo,
            int baudRate,
            int stopBits,
            int parity,
            int flowControl,
            int dataBits,
            String messageId) {
        m_messageId = messageId;
        m_comPortNo = comPortNo;
        m_baudRate = baudRate;
        m_stopBits = stopBits;
        m_parity = parity;
        m_flowControl = flowControl;
        m_dataBits = dataBits;
        m_outputStringBuilder = new StringBuilder();
        m_commandOutputStringBuilder = new StringBuilder();
    }

    private boolean isPortExists() {
        boolean portFound = false;
        String prefix = "";
        if (OsInfo.isWindows()) {
            prefix = "COM";
        } else if (OsInfo.isLinux()) {
            prefix = "/dev/ttyUSB";
        } else {
            throw new RuntimeException(
                    "Running on unsupported OS at SerialPortProvider...");
        }
        String defaultPort = prefix + m_comPortNo;
        Enumeration portList = CommPortIdentifier.getPortIdentifiers();
        while (portList.hasMoreElements()) {
            CommPortIdentifier tempPort = (CommPortIdentifier) portList.nextElement();
            if (tempPort.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                if (tempPort.getName().equals(defaultPort)) {
                    m_portId = tempPort;
                    portFound = true;
                }
            }
        }
        return portFound;
    }

    public boolean startConnection() throws Exception {


        if (!isPortExists()) {
            throw new Exception("COM" + m_comPortNo
                    + " not found...");
        }
        try {
            m_serialPort = (SerialPort) m_portId.open("AtofComReadApp_"
                    + m_comPortNo,
                    2000);
        } catch (PortInUseException e) {
            throw new Exception("COM" + m_comPortNo
                    + " is currently inuse...Error:" + e.getMessage());
        }
        try {
            m_inputStream = m_serialPort.getInputStream();
            m_outputStream = m_serialPort.getOutputStream();
        } catch (IOException e) {
            m_serialPort.close();
            throw new Exception("Streams not handled for COM"
                    + m_comPortNo
                    + "...Error:" + e.getMessage());
        }
        try {
            m_serialPort.addEventListener(this);
        } catch (TooManyListenersException e) {
            throw new Exception("Dont start multiple connection for  COM"
                    + m_comPortNo
                    + "...Error:" + e.getMessage());
        }
        m_serialPort.notifyOnDataAvailable(true);
        try {
            m_serialPort.setSerialPortParams(m_baudRate,
                    m_dataBits,
                    m_stopBits,
                    m_parity);
            m_serialPort.setFlowControlMode(m_flowControl);
            Thread.sleep(4000);
            return true;
        } catch (UnsupportedCommOperationException e) {
            m_serialPort.close();
            throw new Exception("Wrong parameters given to connect COM"
                    + m_comPortNo
                    + "...Error:" + e.getMessage());
        }

    }

    public boolean endConnection() throws Exception {
        if (m_serialPort != null) {
            LogSenderService.putFileMessageOnQueue(
                    "SerialPortProvider",
                    AtofLogLevel.INFO,
                    m_outputStringBuilder.toString(),
                    m_messageId);
            m_serialPort.close();
            return true;
        } else {
            throw new Exception("No connection found for COM"
                    + m_comPortNo);
        }
    }

    public String sendCommand(String cmd) throws Exception {
        if (cmd.trim().equals("Exit")) {
            if (endConnection()) {
                return "Connection closed successfully...";
            } else {
                return "Connection can not closed successfully...";
            }
        }
        try {
            if (m_outputStream != null) {
                m_commandOutputStringBuilder = new StringBuilder();
                m_sendingCommand = true;
                String[] commandList = cmd.split("\r|\n|\r\n|" + System.getProperty("line.separator"));
                for (int i = 0; i < commandList.length; i++) {
                    if (!commandList[i].isEmpty()) {
                        m_outputStream.write(commandList[i].getBytes());
                        m_outputStream.write(System.getProperty("line.separator").getBytes());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException ex) {
                        }
                    }
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                }
                m_sendingCommand = false;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                }
                return m_commandOutputStringBuilder.toString();
            } else {
                throw new Exception("No output stream found for COM"
                        + m_comPortNo);
            }
        } catch (IOException e) {
            throw new Exception("Command  not send to  COM"
                    + m_comPortNo
                    + "...Error:" + e.getMessage());
        }
    }

    public void serialEvent(SerialPortEvent event) {
        switch (event.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;
            case SerialPortEvent.DATA_AVAILABLE:
                StringBuilder sb = new StringBuilder();
                byte[] readBuffer = new byte[1000];

                try {
                    while (m_inputStream.available() > 0) {
                        int numBytes = m_inputStream.read(readBuffer);
                        if (numBytes > 0) {
                            byte[] doneBytes = new byte[numBytes];
                            for (int i = 0; i < numBytes; i++) {
                                doneBytes[i] = readBuffer[i];
                            }
                            sb.append(new String(doneBytes,
                                    "UTF-8"));
                            doneBytes = null; 
                        }
                    }
                    String output = sb.toString();
                    output = output.trim();

                    output = output.trim();
                    if (m_sendingCommand) {
                        m_commandOutputStringBuilder.append(output);
                    }
                    m_outputStringBuilder.append(output);
                    if (m_outputStringBuilder.length() >= 2500) {
                        LogSenderService.putFileMessageOnQueue(
                                "SerialPortProvider",
                                AtofLogLevel.INFO,
                                m_outputStringBuilder.toString(),
                                m_messageId);
                        try {
                            m_outputStringBuilder.delete(0,
                                    m_outputStringBuilder.length() - 1);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                } catch (IOException e) {
                }
                break;
        }
    }

    public String getOutput() {
        return m_outputStringBuilder.toString();
    }
}
